package PDFd.Implementation;

import java.util.HashMap;
import java.util.Map;

import PDFd.Layer.DrawingUtilities;
import PDFd.Layer.Layer;
import PDFd.Layer.LayerMotionEvent;
import PDFd.Layer.Point;
import PDFd.Layer.DrawingUtilities.TouchInputAverager;
import android.graphics.Canvas;


import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;

/**
* <p>Title: DrawingLayer</p>
* <p>Description: A layer that can be drawn on. 
* All methods are defined in interfaces that this implements </p>
* @see LayerMotionEvent
* @see LayerKeyEvent
* @author Matt, Tom, Andrew, Kong
* @email matt.mckay@my.uwrf.edu,tom.florin@my.uwrf.edu,
* andrew.kathan@my.uwrf.edu,kong.xiong-1@my.uwrf.edu
* @date December 1st,2011*/

public class DrawingLayer implements Layer, LayerMotionEvent {
	Point canvasSize, parentOffset, documentOffset;
	float scaleFactor;
	boolean newLine = true;
	Point circleLastCoord;
	int circleLastPress;
	Bitmap b;


	DrawingUtilities.TouchInputAverager tia;
	public DrawingLayer() {
		tia = new TouchInputAverager(10);
	}
	
	
	/**
	 * See interface
	 */
	public void setCanvasSize(Point p) {
		this.canvasSize = p;
	}
	/**
	 * See interface
	 */
	public void setParentOffset(Point p) {
		this.parentOffset = p;
	}
	/**
	 * See interface
	 */
	public Point getCanvasSize() {
		return this.canvasSize;
	}
	/**
	 * See interface
	 */
	public Point getParentOffset() {
		return this.parentOffset;
	}
	/**
	 * See interface
	 */
	public void rasterize(Canvas c) {
		if(b != null) {
			c.drawBitmap(b,parentOffset.x,parentOffset.y,null);
		}
	}
	public void setViewOffset(Point offset) {
		documentOffset = offset;
	}
	/**
	 * Takes the motion event and historical motion events and draws them to this layer.
	 * TODO: test pressure smoothing code
	 */
	public void motionEvent(MotionEvent ev) {
		Point p;
		if (ev.getAction() == MotionEvent.ACTION_DOWN) newLine = true;
		for(int i = 0; i < ev.getHistorySize(); i++) {
			p = new Point((int)ev.getHistoricalX(i),(int)ev.getHistoricalY(i));
			//Account for the offset of the document.
			p = documentOffset.difference(p);
			//Subtract the p offset from the location
			p = p.scale(scaleFactor);
			if(fitsInGlobalSpace(p)) {
				p = parentOffset.difference(p);
				drawLineTo(
						p, 
						(int)(tia.scaleInput(ev.getHistoricalPressure(i))*5));
			}
		}
		p = new Point((int)ev.getX(),(int)ev.getY());
		p = documentOffset.difference(p);
		p = p.scale(scaleFactor);
		if(fitsInGlobalSpace(p)) {
			p = parentOffset.difference(p);
			drawLineTo(
					p,
					(int)(tia.scaleInput(ev.getPressure())*5));
		}
	}
	
	/**
	 * This private method takes in two input, point and pressure
	 * and draws a line to other points.
	 * @param p
	 * @param pressure
	 */
	private void drawLineTo(Point p, int pressure){
		Map<Point, Integer> toDraw = new HashMap<Point,Integer>();	
		if(newLine){
			newLine = !newLine;
			toDraw.put(p, pressure);
		} else {
			toDraw.putAll(DrawingUtilities.getPointsBetween(circleLastCoord, circleLastPress,p,pressure));
		}
		for(Map.Entry<Point, Integer> entry : toDraw.entrySet())
			drawCircle(entry.getKey(), entry.getValue());
		circleLastCoord = p;
		circleLastPress = pressure;
	}
	
	/**
	 * See interface
	 */
	public void finalizeSizeAndOffset() {
		b = Bitmap.createBitmap(canvasSize.x,canvasSize.y,Bitmap.Config.ARGB_8888);
		Log.i("DrawingLayer", "Offset: " + parentOffset + ". Size: "+canvasSize);
	}
	public void layerInFocus(Boolean inFocus) {
	}
	/**
	 * Draw an approximate circle.
	 * @param centerX The center of the circle
	 * @param centerY The center of the circle
	 * @param radius Radius of the circle
	 */
	private void drawCircle(Point p, int radius) {
		int centerX = p.x;
		int centerY = p.y;
		for(int y=-radius; y<=radius; y++)
		    for(int x=-radius; x<=radius; x++)
		        if((x*x)+(y*y) <= radius*radius)
		        	checkAndSet(centerX+x, centerY+y);
	}
	/**
	 * Decides if given X Y coordiates fit inside this space, then sets it active if it is
	 * @param x X
	 * @param y Y
	 */
	private void checkAndSet(int x, int y) {
		if(fitsInLocalCanvas(x,y)) {
			b.setPixel(x,y,Color.BLACK);
		}
	}
	/**
	 * Utility method to ensure that given X Y coordiates fit inside this canvas
	 * @param x
	 * @param y
	 * @return true if it fits inside this canvas
	 */
	private boolean fitsInLocalCanvas(int x, int y) {
		return x > 0 && y > 0 && x < canvasSize.x && y < canvasSize.y;
	}
	/**
	 * Utility method to ensure given X Y coordinates are inside this layer. This is useful when looking at raw motion events.
	 * @param x
	 * @param y
	 * @return true if it fits inside the global space of the view
	 */
	private boolean fitsInGlobalSpace(Point p)  {
		return 
				(p.x > parentOffset.x) && (p.x < (parentOffset.x+canvasSize.x)) &&
				(p.y > parentOffset.y) && (p.y < (parentOffset.y+canvasSize.y));
	}
	/**
	 * See interface
	 */
	@Override
	public String getDisplayName() {
		return "Drawing layer.";
	}
	@Override
	/**
	 * See interface
	 * @param factor
	 */
	public void setScaleFactor(float factor) {
		this.scaleFactor = factor;
	}
}
